#pragma once

#include <functional>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
// #include <atomic>

#include "log.hpp"



class ThreadPool{
private:
    using Task = std::function<void()>;
    std::queue<Task> tasks;
    std::vector<std::thread> workers;
    std::mutex mtx;
    std::condition_variable cv;
    bool stop;

private:
    explicit ThreadPool(size_t n):stop(false){
        if(n <= 0) n = 1;
        for(size_t i = 0; i < n; ++i){
            workers.emplace_back([this]{
                loop();
            });
        }
    }
    ThreadPool(const ThreadPool&) = delete;
    ThreadPool &operator=(const ThreadPool&) = delete;
public:
    static ThreadPool &getInstance(size_t n){
        static ThreadPool instance(n); // C++11保初始化线程安全
        return instance;
    }

public:
    // C++11
    template<class F, class ...Args>
    auto submit(F &&f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
        using R = typename std::result_of<F(Args...)>::type;
        auto task = std::make_shared<std::packaged_task<R()>>(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
        std::future<R> fut = task->get_future();
        {
            std::lock_guard<std::mutex> lk(mtx);
            if(stop){
                WARN_LOG("RunTime Error Throwed: submit on a stoped ThreadPool!\n");
                throw std::runtime_error("submit on a stoped ThreadPool!");
            }
            tasks.emplace([task](){
                (*task)();
            });
        }
        cv.notify_one();
        return std::move(fut);
    }
    ~ThreadPool(){
        {
            std::lock_guard<std::mutex> lk(mtx);
            stop = true;
        }
        cv.notify_all();
        for(auto &worker:workers){
            if(worker.joinable()) worker.join();
        }
    }
private:
    // 1 thread 1 loop
    void loop(){
        for(;;) {
            Task t;
            //Lock Guard
            {
               std::unique_lock<std::mutex> lk(mtx);
               cv.wait(lk, [this]{ return stop || !tasks.empty();});
               if(stop && tasks.empty()) return;
               t = std::move(tasks.front());
               tasks.pop();
            }
            t();
        }
    }
};